msg_tool\scripts\qlie\archive\pack/
mod.rs

1//! Qlie Pack Archive (.pack)
2mod delphi;
3mod encryption;
4mod twister;
5mod types;
6mod v31;
7
8use crate::ext::io::*;
9use crate::scripts::base::*;
10use crate::types::*;
11use crate::utils::struct_pack::*;
12use anyhow::Result;
13use encryption::Encryption;
14use std::io::{Read, Seek, SeekFrom};
15use std::sync::{Arc, Mutex};
16use types::*;
17
18#[derive(Debug)]
19pub struct QliePackArchiveBuilder {}
20
21impl QliePackArchiveBuilder {
22    pub fn new() -> Self {
23        Self {}
24    }
25}
26
27impl ScriptBuilder for QliePackArchiveBuilder {
28    fn default_encoding(&self) -> Encoding {
29        Encoding::Cp932
30    }
31
32    fn default_archive_encoding(&self) -> Option<Encoding> {
33        Some(Encoding::Cp932)
34    }
35
36    fn build_script(
37        &self,
38        data: Vec<u8>,
39        filename: &str,
40        _encoding: Encoding,
41        archive_encoding: Encoding,
42        config: &ExtraConfig,
43        _archive: Option<&Box<dyn Script>>,
44    ) -> Result<Box<dyn Script + Send + Sync>> {
45        Ok(Box::new(QliePackArchive::new(
46            MemReader::new(data),
47            archive_encoding,
48            config,
49            filename,
50        )?))
51    }
52
53    fn build_script_from_file(
54        &self,
55        filename: &str,
56        _encoding: Encoding,
57        archive_encoding: Encoding,
58        config: &ExtraConfig,
59        _archive: Option<&Box<dyn Script>>,
60    ) -> Result<Box<dyn Script + Send + Sync>> {
61        if filename == "-" {
62            let data = crate::utils::files::read_file(filename)?;
63            Ok(Box::new(QliePackArchive::new(
64                MemReader::new(data),
65                archive_encoding,
66                config,
67                filename,
68            )?))
69        } else {
70            let f = std::fs::File::open(filename)?;
71            let reader = std::io::BufReader::new(f);
72            Ok(Box::new(QliePackArchive::new(
73                reader,
74                archive_encoding,
75                config,
76                filename,
77            )?))
78        }
79    }
80
81    fn build_script_from_reader<'a>(
82        &self,
83        reader: Box<dyn ReadSeek + Send + Sync + 'a>,
84        filename: &str,
85        _encoding: Encoding,
86        archive_encoding: Encoding,
87        config: &ExtraConfig,
88        _archive: Option<&Box<dyn Script>>,
89    ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
90        Ok(Box::new(QliePackArchive::new(
91            reader,
92            archive_encoding,
93            config,
94            filename,
95        )?))
96    }
97
98    fn extensions(&self) -> &'static [&'static str] {
99        &["pack"]
100    }
101
102    fn script_type(&self) -> &'static ScriptType {
103        &ScriptType::QliePack
104    }
105
106    fn is_this_format(&self, filename: &str, _buf: &[u8], _buf_len: usize) -> Option<u8> {
107        // Workround: Check only if filename exists in filesystem.
108        // This means that we cannot detect .pack files in another archive.
109        // Pack file header is at the end of file, so we cannot check signature here with header buffer.
110        match is_this_format(filename) {
111            Ok(true) => Some(30),
112            _ => None,
113        }
114    }
115
116    fn is_archive(&self) -> bool {
117        true
118    }
119
120    fn create_archive(
121        &self,
122        filename: &str,
123        files: &[&str],
124        _encoding: Encoding,
125        config: &ExtraConfig,
126    ) -> Result<Box<dyn Archive>> {
127        let f = std::fs::File::create(filename)?;
128        let buf = std::io::BufWriter::new(f);
129        Ok(Box::new(v31::QliePackArchiveWriterV31::new(
130            buf, files, config,
131        )?))
132    }
133}
134
135/// Check if the given file is Qlie Pack Archive format
136pub fn is_this_format<P: AsRef<std::path::Path> + ?Sized>(path: &P) -> Result<bool> {
137    let path = path.as_ref();
138    if !path.exists() || !path.is_file() {
139        return Ok(false);
140    }
141    let mut file = std::fs::File::open(path)?;
142    file.seek(SeekFrom::End(-0x1C))?;
143    let header = QlieHeader::unpack(&mut file, false, Encoding::Utf8, &None)?;
144    Ok(header.is_valid())
145}
146
147#[derive(Debug)]
148pub struct QliePackArchive<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> {
149    header: QlieHeader,
150    encryption: Box<dyn Encryption + Send + Sync>,
151    reader: Arc<Mutex<T>>,
152    qkey: Option<QlieKey>,
153    entries: Vec<QlieEntry>,
154    common_key: Option<Vec<u8>>,
155    _mark: std::marker::PhantomData<&'b ()>,
156}
157
158impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> QliePackArchive<'b, T> {
159    pub fn new(
160        mut reader: T,
161        archive_encoding: Encoding,
162        _config: &ExtraConfig,
163        filename: &str,
164    ) -> Result<Self> {
165        reader.seek(SeekFrom::End(-0x1C))?;
166        let header = QlieHeader::unpack(&mut reader, false, archive_encoding, &None)?;
167        if !header.is_valid() {
168            return Err(anyhow::anyhow!("Invalid Qlie Pack Archive header"));
169        }
170        let file_size = reader.stream_position()?;
171        if header.index_offset > file_size - 0x1C {
172            return Err(anyhow::anyhow!(
173                "Invalid index offset in Qlie Pack Archive header"
174            ));
175        }
176        let major = header.major_version();
177        let minor = header.minor_version();
178        let mut game_key = None;
179        if major == 3 && minor == 0 {
180            game_key = encryption::find_key_data(filename)?;
181        }
182        let mut encryption = encryption::create_encryption(major, minor, game_key)?;
183        // Read key
184        let mut key = 0;
185        let mut qkey = None;
186        if major >= 2 {
187            reader.seek(SeekFrom::End(-0x440))?;
188            let mut qk = QlieKey::unpack(&mut reader, false, archive_encoding, &None)?;
189            if qk.hash_size as u64 > file_size || qk.hash_size < 0x44 {
190                return Err(anyhow::anyhow!("Invalid Qlie Pack Archive key"));
191            }
192            if major >= 3 {
193                key = encryption.compute_hash(&qk.key[..0x100])? & 0xFFFFFFF;
194            }
195            encryption::decrypt(&mut qk.signature, key)?;
196            if &qk.signature != QLIE_KEY_SIGNATURE {
197                eprintln!(
198                    "WARNING: Invalid Qlie Pack Archive key signature, decryption key may be incorrect"
199                );
200                crate::COUNTER.inc_warning();
201            }
202            qkey = Some(qk);
203        }
204        // Read entries
205        let mut entries = if major >= 2 {
206            Self::read_entries(&mut reader, key, &header, archive_encoding, &encryption)?
207        } else {
208            let possible_encs: [Box<dyn Encryption + Send + Sync>; 3] = [
209                Box::new(encryption::Encryption10::new()),
210                Box::new(encryption::Encryption20::new_no_hash()),
211                Box::new(encryption::Encryption20::new()),
212            ];
213            let mut t = None;
214            for enc in possible_encs {
215                match Self::read_entries(&mut reader, key, &header, archive_encoding, &enc) {
216                    Ok(entries) => {
217                        encryption = enc;
218                        t = Some(entries);
219                        break;
220                    }
221                    Err(_) => continue,
222                }
223            }
224            t.ok_or_else(|| {
225                anyhow::anyhow!(
226                    "Failed to read Qlie Pack Archive entries with any encryption method"
227                )
228            })?
229        };
230        let mut common_key = None;
231        if major >= 3 {
232            common_key = encryption::find_game_key(filename)?;
233            if let Some(common_key_entry) = entries.iter_mut().find(|e| e.name == QLIE_KEY_FILE) {
234                reader.seek(SeekFrom::Start(common_key_entry.offset))?;
235                common_key_entry.common_key = common_key.clone();
236                let stream = StreamRegion::with_size(&mut reader, common_key_entry.size as u64)?;
237                let mut decrypted = encryption.decrypt_entry(Box::new(stream), common_key_entry)?;
238                if common_key_entry.is_packed != 0 {
239                    decrypted = encryption::decompress(decrypted)?;
240                }
241                let mut key_data = Vec::new();
242                decrypted.read_to_end(&mut key_data)?;
243                if minor == 1 {
244                    common_key = Some(encryption::get_common_key(&key_data)?);
245                } else {
246                    common_key = Some(key_data);
247                }
248            }
249        }
250        Ok(Self {
251            header,
252            encryption,
253            reader: Arc::new(Mutex::new(reader)),
254            qkey,
255            entries,
256            common_key,
257            _mark: std::marker::PhantomData,
258        })
259    }
260
261    fn read_entries(
262        reader: &mut T,
263        key: u32,
264        header: &QlieHeader,
265        archive_encoding: Encoding,
266        encryption: &Box<dyn Encryption + Send + Sync>,
267    ) -> Result<Vec<QlieEntry>> {
268        let mut entries = Vec::new();
269        reader.seek(SeekFrom::Start(header.index_offset))?;
270        let has_hash = encryption.index_has_hash();
271        for _ in 0..header.file_count {
272            let name_length = reader.read_u16()?;
273            let raw_name_length = if encryption.is_unicode() {
274                name_length as usize * 2
275            } else {
276                name_length as usize
277            };
278            let mut raw_name = reader.read_exact_vec(raw_name_length)?;
279            let name = encryption.decrypt_name(&mut raw_name, key as i32, archive_encoding)?;
280            let offset = reader.read_u64()?;
281            let size = reader.read_u32()?;
282            let unpacked_size = reader.read_u32()?;
283            let is_packed = reader.read_u32()?;
284            let is_encrypted = reader.read_u32()?;
285            let hash = if has_hash { reader.read_u32()? } else { 0 };
286            let entry = QlieEntry {
287                raw_name,
288                name,
289                offset,
290                size,
291                unpacked_size,
292                is_packed,
293                is_encrypted,
294                hash,
295                key,
296                common_key: None,
297            };
298            entries.push(entry);
299        }
300        Ok(entries)
301    }
302}
303
304impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> Script for QliePackArchive<'b, T> {
305    fn default_output_script_type(&self) -> OutputScriptType {
306        OutputScriptType::Json
307    }
308
309    fn default_format_type(&self) -> FormatOptions {
310        FormatOptions::None
311    }
312
313    fn is_archive(&self) -> bool {
314        true
315    }
316
317    fn iter_archive_filename<'a>(
318        &'a self,
319    ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
320        Ok(Box::new(self.entries.iter().map(|e| Ok(e.name.clone()))))
321    }
322
323    fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
324        let mut entry = self
325            .entries
326            .get(index)
327            .ok_or_else(|| anyhow::anyhow!("Invalid file index {} for Qlie Pack Archive", index))?
328            .clone();
329        if self.common_key.is_some() && entry.common_key.is_none() {
330            entry.common_key = self.common_key.clone();
331        }
332        let stream = StreamRegion::with_size(
333            MutexWrapper::new(self.reader.clone(), entry.offset),
334            entry.size as u64,
335        )?;
336        let stream_clone = StreamRegion::with_size(
337            MutexWrapper::new(self.reader.clone(), entry.offset),
338            entry.size as u64,
339        )?;
340        let mut stream = self.encryption.decrypt_entry(Box::new(stream), &entry)?;
341        let mut stream_clone = self
342            .encryption
343            .decrypt_entry(Box::new(stream_clone), &entry)?;
344        if entry.is_packed != 0 {
345            stream = encryption::decompress(stream)?;
346            stream_clone = encryption::decompress(stream_clone)?;
347        }
348        let mut entry = QliePackArchiveContent::new(stream, entry);
349        let mut header_buffer = [0u8; 1024];
350        let readed = stream_clone.read_most(&mut header_buffer)?;
351        entry.typ = detect_script_type(&entry.entry.name, &header_buffer, readed);
352        Ok(Box::new(entry))
353    }
354}
355
356fn detect_script_type(_name: &str, buf: &[u8], buf_len: usize) -> Option<ScriptType> {
357    if super::super::script::is_this_format(buf, buf_len) {
358        return Some(ScriptType::Qlie);
359    }
360    #[cfg(feature = "qlie-img")]
361    {
362        if buf_len >= 4 && buf.starts_with(b"DPNG") {
363            return Some(ScriptType::QlieDpng);
364        }
365        if buf_len >= 6 && buf.starts_with(b"abmp1") {
366            let ver = buf[5];
367            if ver >= b'0' && ver <= b'2' {
368                return Some(ScriptType::QlieAbmp10);
369            }
370        }
371    }
372    None
373}
374
375#[derive(Debug)]
376struct QliePackArchiveContent<T: Read + std::fmt::Debug> {
377    reader: T,
378    entry: QlieEntry,
379    typ: Option<ScriptType>,
380}
381
382impl<T: Read + std::fmt::Debug> QliePackArchiveContent<T> {
383    pub fn new(reader: T, entry: QlieEntry) -> Self {
384        Self {
385            reader,
386            entry,
387            typ: None,
388        }
389    }
390}
391
392impl<T: Read + std::fmt::Debug> Read for QliePackArchiveContent<T> {
393    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
394        self.reader.read(buf)
395    }
396}
397
398impl<T: Read + std::fmt::Debug> ArchiveContent for QliePackArchiveContent<T> {
399    fn name(&self) -> &str {
400        &self.entry.name
401    }
402
403    fn size(&self) -> Option<u64> {
404        Some(self.entry.size as u64)
405    }
406
407    fn script_type(&self) -> Option<&ScriptType> {
408        self.typ.as_ref()
409    }
410}